utils.js ➔ jsonStringify   C
last analyzed

Complexity

Conditions 10
Paths 25

Size

Total Lines 67
Code Lines 52

Duplication

Lines 1
Ratio 1.49 %

Importance

Changes 0
Metric Value
cc 10
eloc 52
nc 25
nop 3
dl 1
loc 67
rs 5.7709
c 0
b 0
f 0

2 Functions

Rating   Name   Duplication   Size   Complexity  
F utils.js ➔ ... ➔ _stringify 0 35 19
A utils.js ➔ ... ➔ repeat 0 3 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like utils.js ➔ jsonStringify often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
'use strict';
2
3
/* eslint-env browser */
4
5
/**
6
 * Module dependencies.
7
 */
8
9
var basename = require('path').basename;
10
var debug = require('debug')('mocha:watch');
11
var exists = require('fs').existsSync;
12
var glob = require('glob');
13
var path = require('path');
14
var join = path.join;
15
var readdirSync = require('fs').readdirSync;
16
var statSync = require('fs').statSync;
17
var watchFile = require('fs').watchFile;
18
var lstatSync = require('fs').lstatSync;
19
var he = require('he');
20
21
/**
22
 * Ignored directories.
23
 */
24
25
var ignore = ['node_modules', '.git'];
26
27
exports.inherits = require('util').inherits;
28
29
/**
30
 * Escape special characters in the given string of html.
31
 *
32
 * @api private
33
 * @param  {string} html
34
 * @return {string}
35
 */
36
exports.escape = function (html) {
37
  return he.encode(String(html), { useNamedReferences: false });
38
};
39
40
/**
41
 * Test if the given obj is type of string.
42
 *
43
 * @api private
44
 * @param {Object} obj
45
 * @return {boolean}
46
 */
47
exports.isString = function (obj) {
48
  return typeof obj === 'string';
49
};
50
51
/**
52
 * Watch the given `files` for changes
53
 * and invoke `fn(file)` on modification.
54
 *
55
 * @api private
56
 * @param {Array} files
57
 * @param {Function} fn
58
 */
59
exports.watch = function (files, fn) {
60
  var options = { interval: 100 };
61
  files.forEach(function (file) {
62
    debug('file %s', file);
63
    watchFile(file, options, function (curr, prev) {
64
      if (prev.mtime < curr.mtime) {
65
        fn(file);
66
      }
67
    });
68
  });
69
};
70
71
/**
72
 * Ignored files.
73
 *
74
 * @api private
75
 * @param {string} path
76
 * @return {boolean}
77
 */
78
function ignored (path) {
79
  return !~ignore.indexOf(path);
80
}
81
82
/**
83
 * Lookup files in the given `dir`.
84
 *
85
 * @api private
86
 * @param {string} dir
87
 * @param {string[]} [ext=['.js']]
88
 * @param {Array} [ret=[]]
89
 * @return {Array}
90
 */
91 View Code Duplication
exports.files = function (dir, ext, ret) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
92
  ret = ret || [];
93
  ext = ext || ['js'];
94
95
  var re = new RegExp('\\.(' + ext.join('|') + ')$');
96
97
  readdirSync(dir)
98
    .filter(ignored)
99
    .forEach(function (path) {
100
      path = join(dir, path);
101
      if (lstatSync(path).isDirectory()) {
102
        exports.files(path, ext, ret);
103
      } else if (path.match(re)) {
104
        ret.push(path);
105
      }
106
    });
107
108
  return ret;
109
};
110
111
/**
112
 * Compute a slug from the given `str`.
113
 *
114
 * @api private
115
 * @param {string} str
116
 * @return {string}
117
 */
118
exports.slug = function (str) {
119
  return str
120
    .toLowerCase()
121
    .replace(/ +/g, '-')
122
    .replace(/[^-\w]/g, '');
123
};
124
125
/**
126
 * Strip the function definition from `str`, and re-indent for pre whitespace.
127
 *
128
 * @param {string} str
129
 * @return {string}
130
 */
131
exports.clean = function (str) {
132
  str = str
133
    .replace(/\r\n?|[\n\u2028\u2029]/g, '\n').replace(/^\uFEFF/, '')
134
    // (traditional)->  space/name     parameters    body     (lambda)-> parameters       body   multi-statement/single          keep body content
135
    .replace(/^function(?:\s*|\s+[^(]*)\([^)]*\)\s*\{((?:.|\n)*?)\s*\}$|^\([^)]*\)\s*=>\s*(?:\{((?:.|\n)*?)\s*\}|((?:.|\n)*))$/, '$1$2$3');
136
137
  var spaces = str.match(/^\n?( *)/)[1].length;
138
  var tabs = str.match(/^\n?(\t*)/)[1].length;
139
  var re = new RegExp('^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs || spaces) + '}', 'gm');
140
141
  str = str.replace(re, '');
142
143
  return str.trim();
144
};
145
146
/**
147
 * Parse the given `qs`.
148
 *
149
 * @api private
150
 * @param {string} qs
151
 * @return {Object}
152
 */
153
exports.parseQuery = function (qs) {
154
  return qs.replace('?', '').split('&').reduce(function (obj, pair) {
155
    var i = pair.indexOf('=');
156
    var key = pair.slice(0, i);
157
    var val = pair.slice(++i);
158
159
    // Due to how the URLSearchParams API treats spaces
160
    obj[key] = decodeURIComponent(val.replace(/\+/g, '%20'));
161
162
    return obj;
163
  }, {});
164
};
165
166
/**
167
 * Highlight the given string of `js`.
168
 *
169
 * @api private
170
 * @param {string} js
171
 * @return {string}
172
 */
173
function highlight (js) {
174
  return js
175
    .replace(/</g, '&lt;')
176
    .replace(/>/g, '&gt;')
177
    .replace(/\/\/(.*)/gm, '<span class="comment">//$1</span>')
178
    .replace(/('.*?')/gm, '<span class="string">$1</span>')
179
    .replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>')
180
    .replace(/(\d+)/gm, '<span class="number">$1</span>')
181
    .replace(/\bnew[ \t]+(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>')
182
    .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '<span class="keyword">$1</span>');
183
}
184
185
/**
186
 * Highlight the contents of tag `name`.
187
 *
188
 * @api private
189
 * @param {string} name
190
 */
191
exports.highlightTags = function (name) {
192
  var code = document.getElementById('mocha').getElementsByTagName(name);
193
  for (var i = 0, len = code.length; i < len; ++i) {
194
    code[i].innerHTML = highlight(code[i].innerHTML);
195
  }
196
};
197
198
/**
199
 * If a value could have properties, and has none, this function is called,
200
 * which returns a string representation of the empty value.
201
 *
202
 * Functions w/ no properties return `'[Function]'`
203
 * Arrays w/ length === 0 return `'[]'`
204
 * Objects w/ no properties return `'{}'`
205
 * All else: return result of `value.toString()`
206
 *
207
 * @api private
208
 * @param {*} value The value to inspect.
209
 * @param {string} typeHint The type of the value
210
 * @returns {string}
211
 */
212
function emptyRepresentation (value, typeHint) {
213
  switch (typeHint) {
214
    case 'function':
215
      return '[Function]';
216
    case 'object':
217
      return '{}';
218
    case 'array':
219
      return '[]';
220
    default:
221
      return value.toString();
222
  }
223
}
224
225
/**
226
 * Takes some variable and asks `Object.prototype.toString()` what it thinks it
227
 * is.
228
 *
229
 * @api private
230
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
231
 * @param {*} value The value to test.
232
 * @returns {string} Computed type
233
 * @example
234
 * type({}) // 'object'
235
 * type([]) // 'array'
236
 * type(1) // 'number'
237
 * type(false) // 'boolean'
238
 * type(Infinity) // 'number'
239
 * type(null) // 'null'
240
 * type(new Date()) // 'date'
241
 * type(/foo/) // 'regexp'
242
 * type('type') // 'string'
243
 * type(global) // 'global'
244
 * type(new String('foo') // 'object'
245
 */
246
var type = exports.type = function type (value) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable type already seems to be declared on line 246. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
247
  if (value === undefined) {
248
    return 'undefined';
249
  } else if (value === null) {
250
    return 'null';
251
  } else if (Buffer.isBuffer(value)) {
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
252
    return 'buffer';
253
  }
254
  return Object.prototype.toString.call(value)
255
    .replace(/^\[.+\s(.+?)]$/, '$1')
256
    .toLowerCase();
257
};
258
259
/**
260
 * Stringify `value`. Different behavior depending on type of value:
261
 *
262
 * - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively.
263
 * - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes.
264
 * - If `value` is an *empty* object, function, or array, return result of function
265
 *   {@link emptyRepresentation}.
266
 * - If `value` has properties, call {@link exports.canonicalize} on it, then return result of
267
 *   JSON.stringify().
268
 *
269
 * @api private
270
 * @see exports.type
271
 * @param {*} value
272
 * @return {string}
273
 */
274 View Code Duplication
exports.stringify = function (value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
275
  var typeHint = type(value);
276
277
  if (!~['object', 'array', 'function'].indexOf(typeHint)) {
278
    if (typeHint === 'buffer') {
279
      var json = Buffer.prototype.toJSON.call(value);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
280
      // Based on the toJSON result
281
      return jsonStringify(json.data && json.type ? json.data : json, 2)
282
        .replace(/,(\n|$)/g, '$1');
283
    }
284
285
    // IE7/IE8 has a bizarre String constructor; needs to be coerced
286
    // into an array and back to obj.
287
    if (typeHint === 'string' && typeof value === 'object') {
288
      value = value.split('').reduce(function (acc, char, idx) {
289
        acc[idx] = char;
290
        return acc;
291
      }, {});
292
      typeHint = 'object';
293
    } else {
294
      return jsonStringify(value);
295
    }
296
  }
297
298
  for (var prop in value) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
299
    if (Object.prototype.hasOwnProperty.call(value, prop)) {
300
      return jsonStringify(exports.canonicalize(value, null, typeHint), 2).replace(/,(\n|$)/g, '$1');
301
    }
302
  }
303
304
  return emptyRepresentation(value, typeHint);
305
};
306
307
/**
308
 * like JSON.stringify but more sense.
309
 *
310
 * @api private
311
 * @param {Object}  object
312
 * @param {number=} spaces
313
 * @param {number=} depth
314
 * @returns {*}
315
 */
316 View Code Duplication
function jsonStringify (object, spaces, depth) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
317
  if (typeof spaces === 'undefined') {
318
    // primitive types
319
    return _stringify(object);
320
  }
321
322
  depth = depth || 1;
323
  var space = spaces * depth;
324
  var str = Array.isArray(object) ? '[' : '{';
325
  var end = Array.isArray(object) ? ']' : '}';
326
  var length = typeof object.length === 'number' ? object.length : Object.keys(object).length;
327
  // `.repeat()` polyfill
328
  function repeat (s, n) {
329
    return new Array(n).join(s);
330
  }
331
332
  function _stringify (val) {
333
    switch (type(val)) {
334
      case 'null':
335
      case 'undefined':
336
        val = '[' + val + ']';
337
        break;
338
      case 'array':
339
      case 'object':
340
        val = jsonStringify(val, spaces, depth + 1);
341
        break;
342
      case 'boolean':
343
      case 'regexp':
344
      case 'symbol':
345
      case 'number':
346
        val = val === 0 && (1 / val) === -Infinity // `-0`
347
          ? '-0'
348
          : val.toString();
349
        break;
350
      case 'date':
351
        var sDate = isNaN(val.getTime()) ? val.toString() : val.toISOString();
352
        val = '[Date: ' + sDate + ']';
353
        break;
354
      case 'buffer':
355
        var json = val.toJSON();
356
        // Based on the toJSON result
357
        json = json.data && json.type ? json.data : json;
358
        val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']';
359
        break;
360
      default:
361
        val = (val === '[Function]' || val === '[Circular]')
362
          ? val
363
          : JSON.stringify(val); // string
364
    }
365
    return val;
366
  }
367
368
  for (var i in object) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
369
    if (!Object.prototype.hasOwnProperty.call(object, i)) {
370
      continue; // not my business
371
    }
372
    --length;
373
    str += '\n ' + repeat(' ', space) +
374
      (Array.isArray(object) ? '' : '"' + i + '": ') + // key
375
      _stringify(object[i]) + // value
376
      (length ? ',' : ''); // comma
377
  }
378
379
  return str +
380
    // [], {}
381
    (str.length !== 1 ? '\n' + repeat(' ', --space) + end : end);
382
}
383
384
/**
385
 * Return a new Thing that has the keys in sorted order. Recursive.
386
 *
387
 * If the Thing...
388
 * - has already been seen, return string `'[Circular]'`
389
 * - is `undefined`, return string `'[undefined]'`
390
 * - is `null`, return value `null`
391
 * - is some other primitive, return the value
392
 * - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method
393
 * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again.
394
 * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()`
395
 *
396
 * @api private
397
 * @see {@link exports.stringify}
398
 * @param {*} value Thing to inspect.  May or may not have properties.
399
 * @param {Array} [stack=[]] Stack of seen values
400
 * @param {string} [typeHint] Type hint
401
 * @return {(Object|Array|Function|string|undefined)}
402
 */
403 View Code Duplication
exports.canonicalize = function canonicalize (value, stack, typeHint) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
404
  var canonicalizedObj;
405
  /* eslint-disable no-unused-vars */
406
  var prop;
407
  /* eslint-enable no-unused-vars */
408
  typeHint = typeHint || type(value);
409
  function withStack (value, fn) {
410
    stack.push(value);
411
    fn();
412
    stack.pop();
413
  }
414
415
  stack = stack || [];
416
417
  if (stack.indexOf(value) !== -1) {
418
    return '[Circular]';
419
  }
420
421
  switch (typeHint) {
422
    case 'undefined':
423
    case 'buffer':
424
    case 'null':
425
      canonicalizedObj = value;
426
      break;
427
    case 'array':
428
      withStack(value, function () {
429
        canonicalizedObj = value.map(function (item) {
430
          return exports.canonicalize(item, stack);
431
        });
432
      });
433
      break;
434
    case 'function':
435
      /* eslint-disable guard-for-in */
436
      for (prop in value) {
0 ignored issues
show
Unused Code introduced by
The variable prop seems to be never used. Consider removing it.
Loading history...
437
        canonicalizedObj = {};
438
        break;
439
      }
440
      /* eslint-enable guard-for-in */
441
      if (!canonicalizedObj) {
442
        canonicalizedObj = emptyRepresentation(value, typeHint);
443
        break;
444
      }
0 ignored issues
show
introduced by
This node falls through to the next case due to this statement. Please add a comment either directly below this line or between the cases to explain.
Loading history...
445
    /* falls through */
446
    case 'object':
447
      canonicalizedObj = canonicalizedObj || {};
448
      withStack(value, function () {
449
        Object.keys(value).sort().forEach(function (key) {
450
          canonicalizedObj[key] = exports.canonicalize(value[key], stack);
451
        });
452
      });
453
      break;
454
    case 'date':
455
    case 'number':
456
    case 'regexp':
457
    case 'boolean':
458
    case 'symbol':
459
      canonicalizedObj = value;
460
      break;
461
    default:
462
      canonicalizedObj = value + '';
463
  }
464
465
  return canonicalizedObj;
0 ignored issues
show
Bug introduced by
The variable canonicalizedObj seems to not be initialized for all possible execution paths.
Loading history...
466
};
467
468
/**
469
 * Lookup file names at the given `path`.
470
 *
471
 * @api public
472
 * @param {string} path Base path to start searching from.
473
 * @param {string[]} extensions File extensions to look for.
474
 * @param {boolean} recursive Whether or not to recurse into subdirectories.
475
 * @return {string[]} An array of paths.
476
 */
477 View Code Duplication
exports.lookupFiles = function lookupFiles (path, extensions, recursive) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
478
  var files = [];
479
  var re = new RegExp('\\.(' + extensions.join('|') + ')$');
480
481
  if (!exists(path)) {
482
    if (exists(path + '.js')) {
483
      path += '.js';
484
    } else {
485
      files = glob.sync(path);
486
      if (!files.length) {
487
        throw new Error("cannot resolve path (or pattern) '" + path + "'");
488
      }
489
      return files;
490
    }
491
  }
492
493
  try {
494
    var stat = statSync(path);
495
    if (stat.isFile()) {
496
      return path;
497
    }
498
  } catch (err) {
499
    // ignore error
500
    return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
501
  }
502
503
  readdirSync(path).forEach(function (file) {
504
    file = join(path, file);
505
    try {
506
      var stat = statSync(file);
507
      if (stat.isDirectory()) {
508
        if (recursive) {
509
          files = files.concat(lookupFiles(file, extensions, recursive));
510
        }
511
        return;
512
      }
513
    } catch (err) {
514
      // ignore error
515
      return;
516
    }
517
    if (!stat.isFile() || !re.test(file) || basename(file)[0] === '.') {
518
      return;
519
    }
520
    files.push(file);
521
  });
522
523
  return files;
524
};
525
526
/**
527
 * Generate an undefined error with a message warning the user.
528
 *
529
 * @return {Error}
530
 */
531
532
exports.undefinedError = function () {
533
  return new Error('Caught undefined error, did you throw without specifying what?');
534
};
535
536
/**
537
 * Generate an undefined error if `err` is not defined.
538
 *
539
 * @param {Error} err
540
 * @return {Error}
541
 */
542
543
exports.getError = function (err) {
544
  return err || exports.undefinedError();
545
};
546
547
/**
548
 * @summary
549
 * This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`)
550
 * @description
551
 * When invoking this function you get a filter function that get the Error.stack as an input,
552
 * and return a prettify output.
553
 * (i.e: strip Mocha and internal node functions from stack trace).
554
 * @returns {Function}
555
 */
556 View Code Duplication
exports.stackTraceFilter = function () {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
557
  // TODO: Replace with `process.browser`
558
  var is = typeof document === 'undefined' ? { node: true } : { browser: true };
559
  var slash = path.sep;
560
  var cwd;
561
  if (is.node) {
562
    cwd = process.cwd() + slash;
563
  } else {
564
    cwd = (typeof location === 'undefined'
565
      ? window.location
566
      : location).href.replace(/\/[^/]*$/, '/');
567
    slash = '/';
568
  }
569
570
  function isMochaInternal (line) {
571
    return (~line.indexOf('node_modules' + slash + 'mocha' + slash)) ||
572
      (~line.indexOf('node_modules' + slash + 'mocha.js')) ||
573
      (~line.indexOf('bower_components' + slash + 'mocha.js')) ||
574
      (~line.indexOf(slash + 'mocha.js'));
575
  }
576
577
  function isNodeInternal (line) {
578
    return (~line.indexOf('(timers.js:')) ||
579
      (~line.indexOf('(events.js:')) ||
580
      (~line.indexOf('(node.js:')) ||
581
      (~line.indexOf('(module.js:')) ||
582
      (~line.indexOf('GeneratorFunctionPrototype.next (native)')) ||
583
      false;
584
  }
585
586
  return function (stack) {
587
    stack = stack.split('\n');
588
589
    stack = stack.reduce(function (list, line) {
590
      if (isMochaInternal(line)) {
591
        return list;
592
      }
593
594
      if (is.node && isNodeInternal(line)) {
595
        return list;
596
      }
597
598
      // Clean up cwd(absolute)
599
      if (/\(?.+:\d+:\d+\)?$/.test(line)) {
600
        line = line.replace(cwd, '');
601
      }
602
603
      list.push(line);
604
      return list;
605
    }, []);
606
607
    return stack.join('\n');
608
  };
609
};
610
611
/**
612
 * Crude, but effective.
613
 * @api
614
 * @param {*} value
615
 * @returns {boolean} Whether or not `value` is a Promise
616
 */
617
exports.isPromise = function isPromise (value) {
618
  return typeof value === 'object' && typeof value.then === 'function';
619
};
620
621
/**
622
 * It's a noop.
623
 * @api
624
 */
625
exports.noop = function () {};
626